#include "Basic/Basic.h"
#include "Basic/UnitTest.h"
#include "Basic/Thread.h"
#include "Collections/List.h"

using namespace zl;
using namespace zl::collections;

#ifdef _MSC_VER
namespace mynamespace
{
	struct ThreadData
	{
		bool modified;

		ThreadData()
			:modified(false)
		{
		}
	};

	void SimpleThreadProc(Thread* thread, void* argument)
	{
		Thread::Sleep(1000);
		((ThreadData*)argument)->modified = true;
	}
}
using namespace mynamespace;

TEST_CASE(TestSimpleThread)
{
	ThreadData data;
	Thread* thread = Thread::CreateAndStart(SimpleThreadProc, &data, false);
	assert(thread->GetState() == Thread::Running);
	assert(thread->Wait() == true);
	assert(thread->GetState() == Thread::Stopped);
	delete thread;
	assert(data.modified == true);
}

TEST_CASE(TestPauseAndStopThread)
{
	{
		ThreadData data;
		Thread* thread = Thread::CreateAndStart(SimpleThreadProc, &data, false);
		assert(thread->GetState() == Thread::Running);
		assert(thread->Pause() == true);
		assert(thread->GetState() == Thread::Paused);
		delete thread;
		assert(data.modified == false);
	}
	{
	ThreadData data;
	Thread* thread = Thread::CreateAndStart(SimpleThreadProc, &data, false);
	assert(thread->GetState() == Thread::Running);
	assert(thread->Stop() == true);
	assert(thread->GetState() == Thread::Stopped);
	delete thread;
	assert(data.modified == false);
}
}

TEST_CASE(TestPauseAndResumeThread)
{
	ThreadData data;
	Thread* thread = Thread::CreateAndStart(SimpleThreadProc, &data, false);
	assert(thread->GetState() == Thread::Running);
	assert(thread->Pause() == true);
	assert(thread->GetState() == Thread::Paused);
	assert(thread->Resume() == true);
	assert(thread->GetState() == Thread::Running);
	assert(thread->Wait() == true);
	assert(thread->GetState() == Thread::Stopped);
	delete thread;
	assert(data.modified == true);
}

/***********************************************************************
Mutex
***********************************************************************/

namespace mynamespace
{
	struct Mutex_ThreadData
	{
		Mutex				mutex;
		volatile zint		counter;

		Mutex_ThreadData()
			:counter(0)
		{
			assert(mutex.Create(true));
		}
	};

	void Mutex_ThreadProc(Thread* thread, void* argument)
	{
		Mutex_ThreadData* data = (Mutex_ThreadData*)argument;
		{
			assert(data->mutex.Wait());
			data->counter++;
			assert(data->mutex.Release());
		}
	}
}
using namespace mynamespace;

TEST_CASE(TestMutex)
{
	Mutex_ThreadData data;
	List<Thread*> threads;
	{
		for (zint i = 0; i<10; i++)
		{
			threads.add(Thread::CreateAndStart(Mutex_ThreadProc, &data, false));
		}
		Thread::Sleep(1000);
		assert(data.counter == 0);
		assert(data.mutex.Release());
	}
	for(auto thread:threads)
	{
		thread->Wait();
		assert(thread->GetState() == Thread::Stopped);
		delete thread;
	}
	assert(data.counter == 10);
}

/***********************************************************************
Semaphore
***********************************************************************/

namespace mynamespace
{
	struct Semaphore_ThreadData
	{
		CriticalSection		cs;
		Semaphore			semaphore;
		volatile zint		counter;

		Semaphore_ThreadData(zint max)
			:counter(0)
		{
			assert(semaphore.Create(0, max));
		}
	};

	void Semaphore_ThreadProc(Thread* thread, void* argument)
	{
		Semaphore_ThreadData* data = (Semaphore_ThreadData*)argument;
		assert(data->semaphore.Wait());
		{
			CriticalSection::Scope lock(data->cs);
			data->counter++;
		}
	}
}
using namespace mynamespace;

TEST_CASE(TestSemaphore)
{
	Semaphore_ThreadData data(10);
	List<Thread*> threads;
	{
		for (zint i = 0; i<10; i++)
		{
			threads.add(Thread::CreateAndStart(Semaphore_ThreadProc, &data, false));
		}
		Thread::Sleep(1000);
		assert(data.counter == 0);
	}
	{
		data.semaphore.Release(3);
		Thread::Sleep(100);
		CriticalSection::Scope lock(data.cs);
		assert(data.counter == 3);
	}
	{
		data.semaphore.Release(4);
		Thread::Sleep(100);
		CriticalSection::Scope lock(data.cs);
		assert(data.counter == 7);
	}
	{
		data.semaphore.Release(3);
		Thread::Sleep(100);
		CriticalSection::Scope lock(data.cs);
		assert(data.counter == 10);
	}
	for(auto thread:threads)
	{
		thread->Wait();
		assert(thread->GetState() == Thread::Stopped);
		delete thread;
	}
}

/***********************************************************************
EventObject
***********************************************************************/

namespace mynamespace
{
	struct Event_ThreadData
	{
		CriticalSection		cs;
		EventObject			eventObject;
		volatile zint		counter;

		Event_ThreadData()
			:counter(0)
		{
			assert(eventObject.CreateManualUnsignal(false));
		}
	};

	void Event_ThreadProc(Thread* thread, void* argument)
	{
		Event_ThreadData* data = (Event_ThreadData*)argument;
		assert(data->eventObject.Wait());
		{
			CriticalSection::Scope lock(data->cs);
			data->counter++;
		}
	}
}
using namespace mynamespace;

TEST_CASE(TestEventObject)
{
	Event_ThreadData data;
	List<Thread*> threads;
	{
		for (zint i = 0; i<10; i++)
		{
			threads.add(Thread::CreateAndStart(Event_ThreadProc, &data, false));
		}
		Thread::Sleep(1000);
		assert(data.counter == 0);
		assert(data.eventObject.Signal());
	}
	for(auto thread:threads)
	{
		thread->Wait();
		assert(thread->GetState() == Thread::Stopped);
		delete thread;
	}
	assert(data.counter == 10);
}

/***********************************************************************
CriticalSection
***********************************************************************/

namespace mynamespace
{
	struct CS_ThreadData
	{
		CriticalSection			cs;
		volatile zint			counter;

		CS_ThreadData()
			:counter(0)
		{
		}
	};

	void CS_ThreadProc(Thread* thread, void* argument)
	{
		CS_ThreadData* data = (CS_ThreadData*)argument;
		{
			CriticalSection::Scope lock(data->cs);
			data->counter++;
		}
	}
}
using namespace mynamespace;

TEST_CASE(TestCriticalSection)
{
	CS_ThreadData data;
	List<Thread*> threads;
	{
		CriticalSection::Scope lock(data.cs);
		for (zint i = 0; i<10; i++)
		{
			threads.add(Thread::CreateAndStart(CS_ThreadProc, &data, false));
		}
		Thread::Sleep(1000);
		assert(data.counter == 0);
	}
	for(auto thread:threads)
	{
		thread->Wait();
		assert(thread->GetState() == Thread::Stopped);
		delete thread;
	}
	assert(data.cs.TryEnter());
	assert(data.counter == 10);
}

/***********************************************************************
ReaderWriterLock
***********************************************************************/

namespace mynamespace
{
	struct SRW_ThreadData
	{
		EventObject				ev;
		SpinLock				sl;
		ReaderWriterLock		lock;
		volatile zint			counter;

		SRW_ThreadData()
			:counter(0)
		{
			ev.CreateManualUnsignal(false);
		}
	};

	void SRW_ReaderProc(Thread* thread, void* argument)
	{
		SRW_ThreadData* data = (SRW_ThreadData*)argument;
		data->ev.Wait();
		for (zint i = 0; i<10; i++)
		{
			ReaderWriterLock::ReaderScope srw(data->lock);
			SpinLock::Scope sl(data->sl);
			data->counter++;
		}
	}

	void SRW_WriterProc(Thread* thread, void* argument)
	{
		SRW_ThreadData* data = (SRW_ThreadData*)argument;
		data->ev.Wait();
		for (zint i = 0; i<10; i++)
		{
			ReaderWriterLock::WriterScope srw(data->lock);
			SpinLock::Scope sl(data->sl);
			data->counter++;
		}
	}
}
using namespace mynamespace;

TEST_CASE(TestReaderWriterLock)
{
	SRW_ThreadData data;
	List<Thread*> threads;
	{
		threads.add(Thread::CreateAndStart(SRW_WriterProc, &data, false));
		for (zint i = 0; i<9; i++)
		{
			threads.add(Thread::CreateAndStart(SRW_ReaderProc, &data, false));
		}
		Thread::Sleep(1000);
		assert(data.counter == 0);
	}
	data.ev.Signal();
	for(auto thread:threads)
	{
		thread->Wait();
		assert(thread->GetState() == Thread::Stopped);
		delete thread;
	}
	assert(data.counter == 100);
}

/***********************************************************************
SpinLock
***********************************************************************/

namespace mynamespace
{
	struct SL_ThreadData
	{
		SpinLock				lock;
		volatile zint			counter;

		SL_ThreadData()
			:counter(0)
		{
		}
	};

	void SL_ThreadProc(Thread* thread, void* argument)
	{
		SL_ThreadData* data = (SL_ThreadData*)argument;
		{
			SpinLock::Scope lock(data->lock);
			data->counter++;
		}
	}
}
using namespace mynamespace;

TEST_CASE(TestSpinLock)
{
	SL_ThreadData data;
	List<Thread*> threads;
	{
		SpinLock::Scope lock(data.lock);
		for (zint i = 0; i<10; i++)
		{
			threads.add(Thread::CreateAndStart(SL_ThreadProc, &data, false));
		}
		Thread::Sleep(1000);
		assert(data.counter == 0);
	}
	for(auto thread:threads)
	{
		thread->Wait();
		assert(thread->GetState() == Thread::Stopped);
		delete thread;
	}
	assert(data.lock.TryEnter());
	assert(data.counter == 10);
}
#else
void ThreadFunc(Thread* thread, void* argument)
{
	wprintf(L"%s",(wchar_t*)argument);
}

void ThreadFunc_PR(Thread* thread, void* argument)
{
	for (zint i = 0; i<10000000; i++)
	{
	}
}

TEST_CASE(TestThread)
{
	Thread* thread = Thread::Create(ThreadFunc, (void*)L"linux thread");
	assert(thread != NULL);
	thread->Wait();
	delete thread;
}

TEST_CASE(TestThreadSuSpendandResume)
{
	Thread* thread = Thread::Create(ThreadFunc_PR, NULL);
	assert(thread != NULL);
	thread->Pause();
	Thread::Sleep(10);
	thread->Resume();
	thread->Wait();
	delete thread;
}
#endif